package chan.project.controller;

import chan.project.manager.QianFanManager;
import chan.project.model.dto.travelPlan.*;
import chan.project.model.entity.Location;
import chan.project.security.BlackListService;
import cn.hutool.json.JSONUtil;
import com.baidubce.qianfan.core.StreamIterator;
import com.baidubce.qianfan.model.chat.ChatResponse;
import com.baidubce.qianfan.model.chat.Message;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import chan.project.annotation.AuthCheck;
import chan.project.common.BaseResponse;
import chan.project.common.DeleteRequest;
import chan.project.common.ErrorCode;
import chan.project.common.ResultUtils;
import chan.project.constant.UserConstant;
import chan.project.exception.BusinessException;
import chan.project.exception.ThrowUtils;
import chan.project.model.entity.TravelPlan;
import chan.project.model.entity.User;
import chan.project.model.vo.TravelPlanVO;
import chan.project.service.TravelPlanService;
import chan.project.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.beans.BeanUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 旅行计划接口
 * @author <a href="https://github.com/kavansp">kavansp</a>
 */
@RestController
@RequestMapping("/travelPlan")
@Slf4j
public class TravelPlanController {

    @Resource
    private TravelPlanService travelPlanService;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private BlackListService blacklistService;

    @Resource
    private QianFanManager qianFanManager;
    // region 增删改查

    /**
     * 创建旅行计划
     *
     * @param travelPlanAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    public BaseResponse<Long> addTravelPlan(@RequestBody TravelPlanAddRequest travelPlanAddRequest, HttpServletRequest request) {
        ThrowUtils.throwIf(travelPlanAddRequest == null, ErrorCode.PARAMS_ERROR);
        TravelPlan travelPlan = new TravelPlan();
        BeanUtils.copyProperties(travelPlanAddRequest, travelPlan);
        // 数据校验
        travelPlanService.validTravelPlan(travelPlan, true);
        User loginUser = userService.getLoginUser(request);
        travelPlan.setUserId(loginUser.getId());
        // 写入数据库
        boolean result = travelPlanService.save(travelPlan);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        // 返回新写入的数据 id
        long newTravelPlanId = travelPlan.getId();
        return ResultUtils.success(newTravelPlanId);
    }

    /**
     * 删除旅行计划
     *
     * @param deleteRequest
     * @param request
     * @return
     */
    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteTravelPlan(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
        if (deleteRequest == null || deleteRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.getLoginUser(request);
        long id = deleteRequest.getId();
        // 判断是否存在
        TravelPlan oldTravelPlan = travelPlanService.getById(id);
        ThrowUtils.throwIf(oldTravelPlan == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可删除
        if (!oldTravelPlan.getUserId().equals(user.getId()) && !userService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 操作数据库
        boolean result = travelPlanService.removeById(id);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    /**
     * 更新旅行计划（仅管理员可用）
     *
     * @param travelPlanUpdateRequest
     * @return
     */
    @PostMapping("/update")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Boolean> updateTravelPlan(@RequestBody TravelPlanUpdateRequest travelPlanUpdateRequest) {
        if (travelPlanUpdateRequest == null || travelPlanUpdateRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        TravelPlan travelPlan = new TravelPlan();
        BeanUtils.copyProperties(travelPlanUpdateRequest, travelPlan);
        // 数据校验
        travelPlanService.validTravelPlan(travelPlan, false);
        // 判断是否存在
        long id = travelPlanUpdateRequest.getId();
        TravelPlan oldTravelPlan = travelPlanService.getById(id);
        ThrowUtils.throwIf(oldTravelPlan == null, ErrorCode.NOT_FOUND_ERROR);
        // 操作数据库
        boolean result = travelPlanService.updateById(travelPlan);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    /**
     * 根据 id 获取旅行计划（封装类）
     *
     * @param id
     * @return
     */
    @GetMapping("/get/vo")
    public BaseResponse<TravelPlanVO> getTravelPlanVOById(long id, HttpServletRequest request) {
        ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        TravelPlan travelPlan = travelPlanService.getById(id);
        ThrowUtils.throwIf(travelPlan == null, ErrorCode.NOT_FOUND_ERROR);
        // 获取封装类
        return ResultUtils.success(travelPlanService.getTravelPlanVO(travelPlan, request));
    }

    /**
     * 分页获取旅行计划列表（仅管理员可用）
     *
     * @param travelPlanQueryRequest
     * @return
     */
    @PostMapping("/list/page")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Page<TravelPlan>> listTravelPlanByPage(@RequestBody TravelPlanQueryRequest travelPlanQueryRequest) {
        long current = travelPlanQueryRequest.getCurrent();
        long size = travelPlanQueryRequest.getPageSize();
        // 查询数据库
        Page<TravelPlan> travelPlanPage = travelPlanService.page(new Page<>(current, size),
                travelPlanService.getQueryWrapper(travelPlanQueryRequest));
        return ResultUtils.success(travelPlanPage);
    }

    /**
     * 分页获取旅行计划列表（封装类）
     *
     * @param travelPlanQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/list/page/vo")
    public BaseResponse<Page<TravelPlanVO>> listTravelPlanVOByPage(@RequestBody TravelPlanQueryRequest travelPlanQueryRequest,
                                                               HttpServletRequest request) {
        long current = travelPlanQueryRequest.getCurrent();
        long size = travelPlanQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<TravelPlan> travelPlanPage = travelPlanService.page(new Page<>(current, size),
                travelPlanService.getQueryWrapper(travelPlanQueryRequest));
        // 获取封装类
        return ResultUtils.success(travelPlanService.getTravelPlanVOPage(travelPlanPage, request));
    }

    /**
     * 分页获取当前登录用户创建的旅行计划列表
     *
     * @param travelPlanQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/my/list/page/vo")
    public BaseResponse<Page<TravelPlanVO>> listMyTravelPlanVOByPage(@RequestBody TravelPlanQueryRequest travelPlanQueryRequest,
                                                                 HttpServletRequest request) {
        ThrowUtils.throwIf(travelPlanQueryRequest == null, ErrorCode.PARAMS_ERROR);
        // 补充查询条件，只查询当前登录用户的数据
        User loginUser = userService.getLoginUser(request);
        travelPlanQueryRequest.setUserId(loginUser.getId());
        long current = travelPlanQueryRequest.getCurrent();
        long size = travelPlanQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<TravelPlan> travelPlanPage = travelPlanService.page(new Page<>(current, size),
                travelPlanService.getQueryWrapper(travelPlanQueryRequest));
        // 获取封装类
        return ResultUtils.success(travelPlanService.getTravelPlanVOPage(travelPlanPage, request));
    }

    /**
     * 编辑旅行计划（给用户使用）
     *
     * @param travelPlanEditRequest
     * @param request
     * @return
     */
    @PostMapping("/edit")
    public BaseResponse<Boolean> editTravelPlan(@RequestBody TravelPlanEditRequest travelPlanEditRequest, HttpServletRequest request) {
        if (travelPlanEditRequest == null || travelPlanEditRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        TravelPlan travelPlan = new TravelPlan();
        BeanUtils.copyProperties(travelPlanEditRequest, travelPlan);
        // 数据校验
        travelPlanService.validTravelPlan(travelPlan, false);
        User loginUser = userService.getLoginUser(request);
        // 判断是否存在
        long id = travelPlanEditRequest.getId();
        TravelPlan oldTravelPlan = travelPlanService.getById(id);
        ThrowUtils.throwIf(oldTravelPlan == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可编辑
        if (!oldTravelPlan.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 操作数据库
        boolean result = travelPlanService.updateById(travelPlan);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    // endregion


    private final static String inputType = "你是一个严谨的旅游专家\n" +
            "我将会给你用户旅游目的的描述\n" +
            "请你根据用户旅游目的的描述提取出所有符合某一个条件或者符合某几个条件的所有地点与地点描述\n" +
            "将所有的地点严格按照以下JSON结构来输出\n" +
            "``` \n" +
            "{\"locationList\":[{\"name\":\"地点名称\",\"description\":\"地点描述\"}]\"allDescription\":\"所有地点的综合描述\"}\n" +
            "```\n" +
            "要求：地点必须是国内的,地点名称要带上省市县等等，比如黄果树瀑布位于广东省深圳市南山区黄果树瀑布" +
            "不要在地点后面输出括号，比如'浙江省舟山市普陀区普陀山南海观音像（可远眺大海）'类似的信息"+
            "locationList是存储所有地点的列表，其中的locationName是地点名，Description是地点描述。AllDescription是指这条旅游线路的综合所见所闻\n" +
            "注意：只需要输出Json字符串，不要输出其它的任何文字";

    /**
     * 生成旅行地点
     * @param planRequirements
     * @return
     */
    @GetMapping(value = "/sendTravelPlan",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter getTravelLocationByStream(String planRequirements,HttpServletRequest request) {
        //todo 获取登录信息为黑名单进行限流
        User loginUser = userService.getLoginUser(request);
        Long userId = loginUser.getId();
        if(blacklistService.isUserInBlacklist(userId)){
            throw new BusinessException(ErrorCode.BLACK_USER_ERROR);
        }
        //接口限流，每秒最多访问两次
        RRateLimiter rateLimiter = redissonClient.getRateLimiter("limitKey");
        rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
        boolean canOp = rateLimiter.tryAcquire(1);
        if(!canOp){
            //todo 某个用户多次触发限流加入黑名单
            RMap<Long, Integer> userLimitMap = redissonClient.getMap("userLimit");
            Integer limitCount = userLimitMap.merge(userId, 1, Integer::sum);
            //如果触发限流超过10次就加入黑名单
            if(limitCount > 10){
                blacklistService.addToBlacklist(userId);
                userLimitMap.delete();
            }
            throw new BusinessException(ErrorCode.RATE_LIMIT_ERROR);
        }
        //设置超时时间
        SseEmitter emitter = new SseEmitter(0L);
        List<Message> messages = new ArrayList<>();
        Message message = new Message();

        message.setRole("user");
        message.setContent(inputType + planRequirements);
        messages.add(message);
        StringBuffer allStringBuffer = new StringBuffer();
        StringBuffer locationStringBuffer = new StringBuffer();
        AtomicReference<Integer> count = new AtomicReference<>(0);

        //自定义池发送请求
        int corePoolSize = 5; // 核心数
        int maximumPoolSize = 10; // 最大线程数
        long keepAliveTime = 600; // 非核心线程超时时间
        TimeUnit unit = TimeUnit.SECONDS; // 时间单位
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                new LinkedBlockingQueue<Runnable>()
        );
        executor.execute(() -> {
            try (StreamIterator<ChatResponse> chatResponseStreamIterator = qianFanManager.ChatByStream(messages)){
                chatResponseStreamIterator.forEachRemaining(chunk -> {
                    try {
                        String plan = chunk.getResult().replaceAll("\\s", "");
                        char[] charArray = plan.toCharArray();
                        for (char c : charArray) {
                            if(c == '{'){
                                count.getAndSet(count.get() + 1);
                            }
                            if(count.get() > 0){
                                allStringBuffer.append(c);
                            }
                            if(count.get() == 2){
                                locationStringBuffer.append(c);
                            }
                            if(c == '}'){
                                count.getAndSet(count.get() - 1);
                                if(count.get() == 1){
                                    System.out.println("发送地点"+locationStringBuffer);
                                    emitter.send(locationStringBuffer);
                                    locationStringBuffer.setLength(0);
                                }
                                if(count.get() == 0){
                                    System.out.println(allStringBuffer);
                                    TravelGuide travelGuide = JSONUtil.toBean(allStringBuffer.toString(), TravelGuide.class);
                                    travelGuide.setPlanRequirements(planRequirements);
                                    System.out.println("发送全部"+travelGuide);
                                    emitter.send(JSONUtil.toJsonStr(travelGuide));
                                }
                            }
                        }
                        //返回总描述
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }
                });
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }

    /**
     * 根据传入地点生成旅行路线
     * @param travelGuide
     * @return
     */
    @PostMapping("/generateTravelPlan")
    public TravelPlan generateTravelPlan(@RequestBody TravelGuide travelGuide,HttpServletRequest request){
        List<Location> locationList = travelGuide.getLocationList();
        if(locationList.size() <= 1){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"当前地点无法生成路线");
        }
        User loginUser = userService.getLoginUser(request);
        String lockKey = "lockKey"+loginUser.getId();
        RLock lock = redissonClient.getLock(lockKey);
        try{
            boolean b = lock.tryLock(5, 60, TimeUnit.SECONDS);
            if(!b){
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "系统繁忙，请重试");
            }
            TravelPlan travelPlan = travelPlanService.generateTravelPlan(travelGuide);
            travelPlan.setUserId(loginUser.getId());
            boolean save = travelPlanService.save(travelPlan);
            if(!save){
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"旅行计划保存失败");
            }
            log.info(travelPlan.toString());
            return travelPlan;
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            lock.unlock();
        }
    }
}
